/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include <string>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"
#include <limits>

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);

void printFunction(int attr, int key, string value, bool tupleMatch, int count)
{
	// print the tuple if there was a match
	if (tupleMatch == true)
	{
		switch (attr)
		{
		case 1:  // SELECT key
			fprintf(stdout, "%d\n", key);
			break;
		case 2:  // SELECT value
			fprintf(stdout, "%s\n", value.c_str());
			break;
		case 3:  // SELECT *
			fprintf(stdout, "%d '%s'\n", key, value.c_str());
			break;
		/*case 4:  // SELECT COUNT(*)
			fprintf(stdout, "%d\n", count);
			break;*/
		case 4: // do nothing
			;
			break;
		}
	}
}

RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
	RecordFile rf;   // RecordFile containing the table
	RecordId   rid;  // record cursor for table scanning
	BTreeIndex idx;  // table index

	RC     rc;
	int    key;     
	string value;
	int    count;
	int    diff;
	
	// for index query
	int 		minKey = -13578;
	int 		maxKey = -13578;
	int 		eqKey  = -13578;
	int			temp;
	string 		stringValue;
	bool 		keyExists = false;
	bool 		stringExists = false;
	bool 		tupleMatch = false;
	bool		firstLT = true;
	bool		firstGT = true;
	bool		firstLE = true;
	bool		firstGE = true;
	bool		equalsDuplicate = false;
	bool		equalsSet = false;
	bool		skip = false;
	bool		onlyInequalityClause = false;
	RecordId 	temprid;
	IndexCursor ic;
	vector<int> notEquals;

	// open the table file
	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
		return rc;
	}

	// open the index file, if possible
	RC indexOpenCode = idx.open(table + ".idx", 'r');
	if (indexOpenCode == 0)
	{
		////////////////////////////////////////
		// Scanning WITH Index /////////////////
		////////////////////////////////////////
		
		rc = 0;
		
		if (attr == 2 || attr == 3) // if attr is value or *, it would be more efficient to use old algorithm if no where clause
		{
			skip = true;
		}
		
		// index should only be used for queries with 1 or more conditions (WHERE) and only if relevant to do so
		if (cond.size() < 1 && skip == true)
		{
			goto exit_index;
		}

		count = 0;
			
		// go in a loop, tuple by tuple
		// start with lowest key needed and end with highest key needed (highest is null if it's >= or >)
		// if there's <>, make sure there's something to check for that
		// count each valid tuple
		
		// look for lowest and highest key needed, if any
		for (int i=0; i < cond.size(); i++)
		{
			if (cond[i].attr == 1) // if it's a key
			{
				if (cond[i].comp == SelCond::EQ)
				{
					if (equalsSet)
					{
						equalsDuplicate = true;
					}
					eqKey = atoi(cond[i].value);
					equalsSet = true;
				}
				if (cond[i].comp == SelCond::LT)
				{
					temp = atoi(cond[i].value)-1;
					if (firstLT)
					{
						maxKey = temp;
						firstLT = false;
					}
					else
					{
						if (temp < maxKey)
						{
							maxKey = temp;
						}
					}
				}
				if (cond[i].comp == SelCond::GT)
				{
					temp = atoi(cond[i].value)+1;
					if (firstGT)
					{
						minKey = temp;
						firstGT = false;
					}
					else
					{
						if (temp > minKey)
							minKey = temp;
					}
				}
				if (cond[i].comp == SelCond::LE)
				{
					temp = atoi(cond[i].value);
					if (firstLE)
					{
						maxKey = temp;
						firstLE = false;
					}
					else
					{
						if (temp < maxKey)
							maxKey = temp;
					}
				}
				if (cond[i].comp == SelCond::GE)
				{
					temp = atoi(cond[i].value);
					if (firstGE)
					{
						minKey = temp;
						firstGE = false;
					}
					else
					{
						if (temp > minKey)
							minKey = temp;
					}
				}
				if (cond[i].comp == SelCond::NE)
				{
					temp = atoi(cond[i].value);
					notEquals.push_back(temp);
				}
				keyExists = true;
			}
			if (cond[i].attr == 2)
			{
				stringValue = cond[i].value;
				stringExists = true;
			}
		}
		
		// only use index if you're looking for a key, not a string value
		if (keyExists == false && skip == true)
		{
			goto exit_index;
		}
		
		// now start searching for the key
		
		if (eqKey != -13578) // query is looking for an exact value
		{
			if (equalsDuplicate)
			{
				goto exit_select;
			}
			idx.locate(eqKey, ic);
			idx.readForward(ic, key, rid);
			if (attr == 2 || attr == 3) // if select statement is looking for value or * (must do read)
			{
				rc = rf.read(rid, key, value);
			}
			if (rc < 0) // status check
			{
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}
			if (key == eqKey)
			{
				if (stringExists == true)
				{
					if (stringValue == value)
					{
						tupleMatch = true;
						count++;
					}
					else
					{
						tupleMatch = false;
					}
				}
				else
				{
					tupleMatch = true;
					count++;
				}
			}
			for (int k=0; k<notEquals.size(); k++)
			{
				if (key == notEquals[k])
					tupleMatch = false;
			}
			printFunction(attr, key, value, tupleMatch, count);
		}
		else if (minKey != -13578 && maxKey != -13578) // range
		{
			rc = 0;
			idx.locate(minKey, ic);
			do
			{
				if (idx.readForward(ic, key, rid) < 0)
					break;
				
				if (attr == 2 || attr == 3) // if select statement is looking for value or * (must do read)
				{
					rc = rf.read(rid, key, value);
				}
				/*if (temprid == rid)
					break;
				temprid = rid;
				*/
				if (rc < 0) // status check
				{
					fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
					goto exit_select;
				}
				if (key <= maxKey)
				{
					if (stringExists == true)
					{
						if (stringValue == value)
						{
							tupleMatch = true;
							count++;
						}
						else
						{
							tupleMatch = false;
						}
					}
					else
					{
						tupleMatch = true;
						count++;
					}
					for (int k=0; k<notEquals.size(); k++)
					{
						if (key == notEquals[k])
						tupleMatch = false;
					}
					printFunction(attr, key, value, tupleMatch, count);
				}
			}
			while (key <= maxKey);
		}
		else if (minKey != -13578) // minimum-bound
		{
			rc = 0;
			idx.locate(minKey, ic);
			do
			{
				if (idx.readForward(ic, key, rid) < 0)
					break;
				if (attr == 2 || attr == 3) // if select statement is looking for value or * (must do read)
				{
					rc = rf.read(rid, key, value);
				}
				/*
				if (temprid == rid)
					break;
				temprid = rid;*/
				if (rc < 0) // status check
				{
					fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
					goto exit_select;
				}
				if (rc == 0)
				{
					if (stringExists == true)
					{
						if (stringValue == value)
						{
							tupleMatch = true;
							count++;
						}
						else
						{
							tupleMatch = false;
						}
					}
					else
					{
						tupleMatch = true;
						count++;
					}
					for (int k=0; k<notEquals.size(); k++)
					{
						if (key == notEquals[k])
						tupleMatch = false;
					}
					printFunction(attr, key, value, tupleMatch, count);
				}
			}
			while (rc == 0);
		}
		else if (maxKey != -13578) // maximum-bound
		{
			rc = 0;
			minKey = INT_MIN;
			idx.locate(minKey, ic);
			do
			{
				if (idx.readForward(ic, key, rid) < 0)
					break;
				if (attr == 2 || attr == 3) // if select statement is looking for value or * (must do read)
				{
					rc = rf.read(rid, key, value);
				}
				/*if (temprid == rid)
					break;
				temprid = rid; */
				if (rc < 0) // status check
				{
					fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
					goto exit_select;
				}
				if (key <= maxKey)
				{
					if (stringExists == true)
					{
						if (stringValue == value)
						{
							tupleMatch = true;
							count++;
						}
						else
						{
							tupleMatch = false;
						}
					}
					else
					{
						tupleMatch = true;
						count++;
					}
					for (int k=0; k<notEquals.size(); k++)
					{
						if (key == notEquals[k])
						tupleMatch = false;
					}
					printFunction(attr, key, value, tupleMatch, count);
				}
			}
			while (key <= maxKey);
		}
		else // if no where clause but it's looking for key or count(*)
		{
			if (cond.size() > 0) // this means the where clause must only have inequality conditions. don't have to use index accordin to specs
				goto exit_index;
			rc = 0;
			minKey = INT_MIN;
			idx.locate(minKey, ic);
			do
			{
				rc = idx.readForward(ic, key, rid);
				if (rc < 0)
					break;
				/*if (attr == 2 || attr == 3) // if select statement is looking for value or * (must do read)
				{
					rc = rf.read(rid, key, value);
				}*/
				/*if (temprid == rid)
					break;
				temprid = rid; */
				if (rc < 0) // status check
				{
					fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
					goto exit_select;
				}
				tupleMatch = true;
				for (int k=0; k<notEquals.size(); k++)
				{
					if (key == notEquals[k])
						tupleMatch = false;
				}
				if (tupleMatch == true)
					count++;
				if (attr == 1) // if its looking for key, print it
					printFunction(attr, key, value, tupleMatch, count);
			}
			while (rc == 0);
		}
		
		if (attr == 4) // count
		{
			fprintf(stdout, "%d\n", count);
		}	
		
		rc = 0;
		rf.close();
		idx.close();
		return rc;
		
	}
	
	exit_index:
	
	////////////////////////////////////////
	// Scanning WITHOUT Index //////////////
	////////////////////////////////////////
	
	// scan the table file from the beginning
	rid.pid = rid.sid = 0;
	count = 0;
	while (rid < rf.endRid()) {
		// read the tuple
		if ((rc = rf.read(rid, key, value)) < 0) {
		  fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
		  goto exit_select;
		}

		// check the conditions on the tuple
		for (unsigned i = 0; i < cond.size(); i++) {
		  // compute the difference between the tuple value and the condition value
		  switch (cond[i].attr) {
		  case 1:
		diff = key - atoi(cond[i].value);
		break;
		  case 2:
		diff = strcmp(value.c_str(), cond[i].value);
		break;
		  }

		  // skip the tuple if any condition is not met
		  switch (cond[i].comp) {
		  case SelCond::EQ:
		if (diff != 0) goto next_tuple;
		break;
		  case SelCond::NE:
		if (diff == 0) goto next_tuple;
		break;
		  case SelCond::GT:
		if (diff <= 0) goto next_tuple;
		break;
		  case SelCond::LT:
		if (diff >= 0) goto next_tuple;
		break;
		  case SelCond::GE:
		if (diff < 0) goto next_tuple;
		break;
		  case SelCond::LE:
		if (diff > 0) goto next_tuple;
		break;
		  }
		}

		// the condition is met for the tuple. 
		// increase matching tuple counter
		count++;

		// print the tuple 
		switch (attr) {
		case 1:  // SELECT key
		  fprintf(stdout, "%d\n", key);
		  break;
		case 2:  // SELECT value
		  fprintf(stdout, "%s\n", value.c_str());
		  break;
		case 3:  // SELECT *
		  fprintf(stdout, "%d '%s'\n", key, value.c_str());
		  break;
		}

		// move to the next tuple
		next_tuple:
		++rid;
	}

	// print matching tuple count if "select count(*)"
	if (attr == 4) {
		fprintf(stdout, "%d\n", count);
	}
	rc = 0;
	
	exit_select:
	rf.close();
	idx.close();
	return rc;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	RecordFile rf;   // RecordFile containing the table
	RecordId rid; 	 // record cursor for table scanning
	RC rc;
	
	ifstream ifs;
	const char *load = loadfile.c_str();
	ifs.open(load, ifstream::in);
	
	string line;
	int key;
	string value;
	int count = 0;
	
	rc = rf.open(table + ".tbl", 'w');
	
	// create index if specified in command
	BTreeIndex idx;
	if (index)
	{
//		cout << "Creating index..." << endl;
		idx.open(table + ".idx", 'w');
	}
	

	while(getline(ifs, line))
	{
		if(!line.empty())
		{
			rc = parseLoadLine(line, key, value);
			// append to RecordFile table
			RC appendCode = rf.append(key, value, rid);
			if (appendCode != 0)
			{
				return RC_FILE_WRITE_FAILED;
			}
			// add key/value pair to index
			if (index) // having this option adds an extra page for some reason...
			{
				RC insertionCode = idx.insert(key, rid);
				if (insertionCode != 0)
				{
					return RC_FILE_WRITE_FAILED;
				}
			}
			count = count + 1;
		}
	}

	fprintf(stdout, "%d records inserted\n", count);
	
	ifs.close();
	rf.close();
	if (index)
	{
		idx.close();
	}
	return 0;
}

